#include "GameObjectManager.h"
#include "Game.h"


template <class T> GameObject* GetNewInstance() { return GL_NEW T(); }
typedef GameObject* (*newGameObjectFn)();
struct ObjectCreationMapItem 
{
	const char*      objectTypeName;
	newGameObjectFn  spawnFn;
};
const ObjectCreationMapItem objectCreationMap[] = {
	{ "RobotCleaner", GetNewInstance<Robot> },
	{ "Bullet", GetNewInstance<ThrowableObj> },
	{ "Balloon", GetNewInstance<Balloon> },
	
};

GameObject*
GameObjectManager::GetNewObject(  const char* name)
{
	const unsigned objectCreationMapSize = sizeof(objectCreationMap)/sizeof(ObjectCreationMapItem);
	for ( unsigned i = 0 ; i < objectCreationMapSize ; i++ )
	{
		if ( strcmp( name, objectCreationMap[i].objectTypeName ) == 0 )
		{
			GameObject* obj = objectCreationMap[i].spawnFn();
			ASSERT(obj);
			if ( obj )
			{
				GetGameObjects().AddObject( obj );
				return obj;
			}
		}
	}

	//TRACE("Error: Unknown game object type (%s)\n", type);

	return NULL;	
}

GameObject* GameObjectManager::Spawn( const char* name)
{
	GameObject* h = GetNewObject( name );
	h->Initialize();
	h->InitPhysics();
	return h;
}
GameObjectManager::GameObjectManager(void)
{
	m_gameObjects.Reserve(MAX_NUM_GAME_OBJECTS);
	m_farRangeObjects.Reserve(MAX_NUM_FARRANGE_OBJECTS);

	InitPhysics();
}

GameObjectManager::~GameObjectManager(void)
{
}

void GameObjectManager::SaveData(CWriteFile* stream)
{
	for(int i =0;i<m_gameObjects.GetCount();i++)
	{
		m_gameObjects[i]->SaveData(stream);
	}
}

void GameObjectManager::LoadData(CReadFile* stream)
{
	for(int i =0;i<m_gameObjects.GetCount();i++)
	{
		m_gameObjects[i]->LoadData(stream);
	}
}
void GameObjectManager::InitializeGameObjects()
{
	for(int i = 0; i < m_gameObjects.GetCount(); i++)
	{
		GameObject* obj = m_gameObjects[i];
		obj->Initialize();
	}
}

b2World&         
GameObjectManager::GetPhysicsWorld() 
{
	ASSERT(m_pBox2DWorld);
	return *m_pBox2DWorld;
}

class GameContactListener : public b2ContactListener
{
public:
	void Add(const b2ContactPoint* point)
    {
		GameObject* obj1 = (GameObject*)point->shape1->GetBody()->GetUserData();
		GameObject* obj2 = (GameObject*)point->shape2->GetBody()->GetUserData();
        
		if ( obj1 && obj2 && ( obj1 != obj2 ) )
		{
			obj1->AddContact( obj2 );
			obj2->AddContact( obj1 );
		}
    }
    	
	void Remove(const b2ContactPoint* point) 
	{
		/*GameObject* obj1 = (GameObject*)point->shape1->GetBody()->GetUserData();
		if ( obj1 )
		{
			obj1->FlagIntersectionRemovalEvent();
		}

		GameObject* obj2 = (GameObject*)point->shape2->GetBody()->GetUserData();
		if ( obj2 )
		{
			obj2->FlagIntersectionRemovalEvent();
		}*/
	}

	//void Persist(const b2ContactPoint* point){}
	
	void Result(const b2ContactResult* point)
	{
		GameObject* obj1 = (GameObject*)point->shape1->GetBody()->GetUserData();
		GameObject* obj2 = (GameObject*)point->shape2->GetBody()->GetUserData();
        
		if ( obj1 && obj2 && ( obj1 != obj2 ) )
		{
			obj1->AddCollision( obj2, point->position, -point->normal, point->normalImpulse );
			obj2->AddCollision( obj1, point->position, point->normal, point->normalImpulse );
		}
	}
};

class GameBoundaryListener : public b2BoundaryListener
{
public:
	virtual void  Violation (b2Body *body)
	{
		GameObject* obj = (GameObject*)body->GetUserData();
		if ( obj )
		{
			//TRACE( "Warning: object beyond world limits: %s\n", obj->GetName() );
		}
	}
};

class GameContactFilter : public b2ContactFilter
{
	virtual bool ShouldCollide(b2Shape* shape1, b2Shape* shape2)
	{
		if(  b2_defaultFilter.ShouldCollide(shape1, shape2) )
		{
			if ( shape1->IsSensor() || shape2->IsSensor() )
			{
 				return true;
			}

			GameObject* obj1 = (GameObject*)shape1->GetBody()->GetUserData();
			GameObject* obj2 = (GameObject*)shape2->GetBody()->GetUserData();

			return obj1 && obj2 &&
				//( ! obj1->GetCollisionIgnoreList().Contains( obj2 ) ) &&
				//( ! obj2->GetCollisionIgnoreList().Contains( obj1 ) ) &&
				obj1->ShouldCollideWith( obj2 ) && obj2->ShouldCollideWith( obj1 );
		}
		else
		{
			return false;
		}
	}
};



void GameObjectManager::InitPhysics()
{
	if(m_pBox2DWorld)
	{
		//DestroyPhysics();
	}

	b2AABB worldAABB;
	worldAABB.lowerBound.Set(-200.0f, -100.0f);
	worldAABB.upperBound.Set(200.0f, 200.0f);

	// Define the gravity vector.
	b2Vec2 gravity(0,-10.0f);

	m_pBox2DWorld = GL_NEW b2World(worldAABB, gravity, true);

	m_physicsContactListener = GL_NEW GameContactListener();
	m_pBox2DWorld->SetContactListener( m_physicsContactListener );

	m_physicsContactFilter = GL_NEW GameContactFilter();
	m_pBox2DWorld->SetContactFilter( m_physicsContactFilter );

	m_physicsBoundaryListener = GL_NEW GameBoundaryListener();
	m_pBox2DWorld->SetBoundaryListener( m_physicsBoundaryListener );

	//m_pBox2DWorld->SetDebugDraw(&m_debugDraw);
	m_pBox2DWorld->SetDebugDraw(&m_debugDraw);

	uint32 flags = 0;
	flags +=  b2DebugDraw::e_shapeBit;
	//flags += settings->drawJoints			* b2DebugDraw::e_jointBit;
	//flags += settings->drawCoreShapes		* b2DebugDraw::e_coreShapeBit;
	//flags += settings->drawAABBs			* b2DebugDraw::e_aabbBit;
	//flags += settings->drawOBBs				* b2DebugDraw::e_obbBit;
	//flags += settings->drawPairs			* b2DebugDraw::e_pairBit;
	//flags += settings->drawCOMs				* b2DebugDraw::e_centerOfMassBit;
	m_debugDraw.SetFlags(flags);
}

void GameObjectManager::UpdateGameObjects()
{
	const int objCount = m_gameObjects.GetCount();
	int i=objCount-1;
	while(i>=0)
	{
		GameObject* obj = m_gameObjects[i];
		if( obj->IsNeedDespawn())
		{

			m_gameObjects.RemoveObject(obj);
			SAFE_DELETE(obj);
		}
		i--;
	}

	UpdateGameObjectsStep1();
	UpdateGameObjectsStep2();
	UpdatePhysics( getGame()->GetGameplayTimeStep() );
	UpdateGameObjectsStep3();
}

void          
GameObjectManager::UpdatePhysics( float timestep )
{
	ASSERT( m_pBox2DWorld );
	if ( m_pBox2DWorld )
	{
		const int iterations = 10;
		m_pBox2DWorld->Step( timestep, iterations );
	}
}

void GameObjectManager::UpdateGameObjectsStep1()
{

	m_farRangeObjects.Clear();    // objects in visible range

	const int objCount = m_gameObjects.GetCount();

	for(int i = 0; i < objCount; ++i)
	{
		GameObject* obj = m_gameObjects[i];
		UpdateObjectAttributes(obj);
	}

	for(int i = 0; i < m_gameObjects.GetCount(); ++i)
	{
		GameObject* obj = m_gameObjects[i];
		if(getGame()->IsTimeFrezze() && 
			(obj->GetObjectType() == Object_Robot ||
			obj->GetObjectType() == Object_ThrowableObj))
			continue;
		if ( obj->IsEnabled() )
		{
			obj->Update();			
		}
	}
}


void GameObjectManager::UpdateGameObjectsStep2()
{
	const int objCount = m_farRangeObjects.GetCount();
	GameObjectListInterface& objs = m_farRangeObjects;

	//Pre-Collision Update
	for(int i = 0; i < objCount; ++i)
	{
		if(getGame()->IsTimeFrezze() && 
			(objs[i]->GetObjectType() == Object_Robot ||
			objs[i]->GetObjectType() == Object_ThrowableObj))
			continue;
		objs[i]->MasterUpdate_PreCollision();
	}
}

void GameObjectManager::UpdateGameObjectsStep3()
{
	const int objCount = m_farRangeObjects.GetCount();
	for(int i = 0; i < objCount; i++)
	{
		GameObject* obj = m_farRangeObjects[i];

		if(getGame()->IsTimeFrezze() && 
			(obj->GetObjectType() == Object_Robot ||
			obj->GetObjectType() == Object_ThrowableObj))
			continue;

		if ( obj->IsUpdatable() )
		{
			obj->Update_PostCollision();
		}
	}
}

void GameObjectManager::UpdateObjectAttributes(GameObject* obj)
{
	obj->SetInFarRange( false );

	
	/*if(obj->GetIsoX() >= -TILE_W && obj->GetIsoX() <= SCREEN_WIDTH + TILE_W &&
		obj->GetIsoY() >= -TILE_W && obj->GetIsoY() <= SCREEN_HEIGHT + TILE_W )*/
	{
		m_farRangeObjects.AddObject(obj);
		obj->SetInFarRange(true);
		obj->SetUpdatable(true);		
	}


	//obj->DoMandatoryUpdate();
}

void GameObjectManager::DrawGameObjects()
{
	ScreenManager* ScreenManager = gp_GameApp->GetScreenManager();
	GameObjectListInterface& objs = GetGameObjects();
	int objCount = objs.GetCount();
	for ( int i = 0 ; i < objCount ; ++i )
	{
		GameObject* obj = objs[i];
		if ( obj->IsRenderable() && obj->IsInFarRange() )
		{
			ScreenManager->registerObjectForRendering(obj);			
		}
	}

	//glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

	//glMatrixMode(GL_PROJECTION);
	//glLoadIdentity();
	//float32 ratio = float32(SCREEN_WIDTH) / float32(SCREEN_HEIGHT);

	//b2Vec2 extents(ratio * HTALL_BY_METERS, HTALL_BY_METERS);
	////extents *= viewZoom;
	//b2Vec2 viewCenter(ratio * HTALL_BY_METERS,HTALL_BY_METERS * 0.6f);
	//b2Vec2 lower = viewCenter - extents;
	//b2Vec2 upper = viewCenter + extents;

	//// L/R/B/T
	//glOrtho(lower.x, upper.x, lower.y, upper.y,-500,500);
	m_pBox2DWorld->DrawDebugData();

	/*glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0, SCREEN_WIDTH, 0, SCREEN_HEIGHT, -250.0f, 250.0f);*/
}
